10  Pandas Series和DataFrame创建

10.1 引言Pandas在金融数据分析中的核心地位

Pandas是Python数据分析的基石库,建立在NumPy之上,提供了专为数据处理设计的高级数据结构和分析工具。在金融领域,Pandas几乎是事实上的标准,从数据清洗到复杂的时间序列分析,从简单的统计计算到复杂的金融建模,Pandas都能胜任。

10.2 本章学习目标

通过本章学习,你将能够:

  1. 说出 Series 与 DataFrame 的结构组成(值、索引、列标签),并解释 Pandas 相比 Excel 在可重复性与处理规模上的优势
  2. 使用 pd.Series(data, index=...)pd.DataFrame(data, index=...) 从列表、字典等创建对象,并为金融数据设置有含义的行索引(如交易日)
  3. 使用 [].loc.iloc 访问列、行与单个值,并用布尔索引与 query 完成条件筛选
  4. 使用 mean/std/describe 等统计函数按行或按列(axis)计算指标,并解释索引对齐运算中 NaN 的来源
  5. 识别并初步处理缺失值(isnull/dropna/fillna),为第 章节 16 章的数据清洗打基础

先修内容:第 章节 5 章至第 章节 7 章的 NumPy 数组基础。

10.3 Pandas简介与安装

理论背景:Pandas的设计哲学

Pandas的设计深受R语言data.frame的影响,并结合了NumPy的高性能计算能力和Python的灵活性:

  • DataFrame: 二维表格数据结构(类似Excel表格)
  • Series: 一维带标签数组(类似DataFrame的单列)
  • Index: 行标签,支持时间序列索引
  • 向量化操作: 类似NumPy的高性能运算

为什么Pandas优于Excel?

  1. 处理大数据: Excel限制约100万行,Pandas几乎无限制
  2. 可重复性: 代码可以重现,Excel操作难以追踪
  3. 自动化: 可以构建自动化分析流程
  4. 复杂计算: 支持复杂的分组、聚合、变换操作
  5. 时间序列: 专门优化的时间序列处理能力

带着这一设计脉络,我们先完成Pandas的导入与安装验证:

列表 10.1: 导入Pandas库
# =============================================================================
# 题目:导入Pandas库并验证安装
# =============================================================================
# 本代码块演示Pandas库的标准导入方式,并验证其是否正常工作。
# 在金融数据分析中,Pandas是核心工具库,用于处理结构化数据。
# =============================================================================

# ==================== 导入Pandas库 ====================
import pandas as pd  # 导入Pandas库,使用pd作为别名(行业惯例)
import numpy as np  # 导入NumPy库,Pandas依赖NumPy进行数值计算

# ==================== 查看Pandas版本 ====================
print(f"Pandas版本: {pd.__version__}")  # 打印Pandas版本号,用于确认环境

# ==================== 测试Pandas是否正常工作 ====================
test_data = pd.Series([1, 2, 3, 4, 5])  # 创建测试Series对象
print(f"\n测试数据:\n{test_data}")  # 打印测试数据,验证Pandas功能正常
# 输出解释:显示包含整数1-5的Series,左侧为索引(0-4),右侧为值

安装命令:

# 使用conda
conda install pandas

# 使用pip
pip install pandas

10.4 Series一维带标签数组

10.4.1 创建Series

Series是Pandas的一维数据结构,类似NumPy数组但带标签索引。

语法: pd.Series(data, index=index, dtype=dtype, name=name)

任务要求:用列表 list_may(四只股票某日的涨跌幅)与列表 name(对应的股票名称)创建带标签索引的 Series s,并打印输出。请将代码原样输入教学平台(注释除外),判定以平台为准。

列表 10.2: 平台原始代码 (1)
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#任务一:创建股票涨跌幅Series
import pandas as pd
list_may=[-0.0035,-0.0338,0.0058,-0.0298]  # 定义列表list_may
name=['中国卫星','中国软件','中国银行','上汽集团']  # 定义列表name
s=pd.Series(list_may,index=name)  # 创建Series序列s
print(s)  # 输出任务一:创建股票涨跌幅Series

预期输出(本机 Python 实际运行结果,具体以平台运行结果为准):

中国卫星   -0.0035
中国软件   -0.0338
中国银行    0.0058
上汽集团   -0.0298
dtype: float64

代码深度解析:

  1. Series的组成:

    • Values: 底层的数据(NumPy数组)
    • Index: 行标签(可以是字符串、数字、日期等)
    • Name: Series的名称(可选)
  2. 查看Series属性:

    print(f"值:\n{s.values}")
    print(f"索引:\n{s.index}")
    print(f"名称: {s.name}")
    print(f"数据类型: {s.dtype}")
  3. 索引的意义:

    • 快速查找: 类似字典的键值查找
    • 数据对齐: 自动按索引对齐运算
    • 时间序列: 支持日期时间索引

10.4.2 Series的基本操作

列表 10.3: Series的基本操作和访问
# =============================================================================
# 题目:Series的基本操作、数据访问和统计分析
# =============================================================================
# 本代码块演示Series的常用操作,包括索引访问、条件筛选、统计摘要和排序。
# 金融应用场景:分析股票涨跌幅,筛选负收益股票,计算统计指标。
# =============================================================================

# ==================== 创建示例Series ====================
s = pd.Series(  # 创建Series对象
    [-0.0035, -0.0338, 0.0058, -0.0298],  # 数据:四只股票涨跌幅
    index=['中国卫星', '中国软件', '中国银行', '上汽集团'],  # 索引:股票名称
    name='涨跌幅'  # Series名称
)

print("完整Series:")  # 打印标题
print(s)  # 显示完整的Series对象

# ==================== 1. 通过索引访问单个元素 ====================
print(f"\n中国软件涨跌幅: {s['中国软件']:.4f}")  # 使用索引标签访问单个元素
# 输出解释:.4f格式化为保留4位小数,显示-0.0338

# ==================== 2. 通过位置访问(类似NumPy) ====================
print(f"第一个元素: {s[0]:.4f}")  # 使用整数位置访问第一个元素
print(f"前两个元素:\n{s[:2]}")  # 使用切片访问前两个元素(位置0和1)
# 输出解释:显示中国卫星和中国软件的涨跌幅

# ==================== 3. 布尔索引(条件筛选) ====================
print(f"\n负收益股票:\n{s[s < 0]}")  # 使用布尔条件筛选负收益股票
# 输出解释:显示涨跌幅小于0的三只股票(中国卫星、中国软件、上汽集团)

# ==================== 4. 统计摘要 ====================
print(f"\n统计摘要:")
print(s.describe())  # 显示描述性统计信息
# 输出解释:包含count(数量)、mean(均值)、std(标准差)、min(最小值)、
#          25%(下四分位数)、50%(中位数)、75%(上四分位数)、max(最大值)

# ==================== 5. 向量化运算 ====================
s_doubled = s * 2  # Series乘以标量,所有元素都乘以2(向量化操作)
print(f"\n涨跌幅翻倍:")
print(s_doubled)
# 输出解释:每个涨跌幅值都翻倍,如-0.0035变为-0.0070

# ==================== 6. 排序 ====================
s_sorted = s.sort_values()  # 按值升序排序(默认)
print(f"\n按值排序(升序):")
print(s_sorted)
# 输出解释:从最负到最正排序,中国软件(-0.0338)排在最前

s_sorted_desc = s.sort_values(ascending=False)  # 按值降序排序
print(f"\n按值排序(降序):")
print(s_sorted_desc)
# 输出解释:从最正到最负排序,中国银行(0.0058)排在最前

补充说明:索引 vs 位置

访问方式 语法 说明 示例
标签索引 s['label'] 使用索引名 s['中国卫星']
位置索引 s[0] 使用整数位置 s[0]
切片(标签) s['a':'c'] 包含两端 s['A':'C']
切片(位置) s[0:2] 不包含末端 s[0:2]

易混淆点: 标签切片包含末端,位置切片不包含!

10.5 DataFrame二维表格数据

10.5.1 创建DataFrame

DataFrame是Pandas的二维数据结构,类似Excel表格或SQL表。

语法: pd.DataFrame(data, index=index, columns=columns)

任务要求:用字典 data(四只股票各 5 个交易日的涨跌幅)与列表 index(五个交易日)创建 DataFrame df,并打印输出。请将代码原样输入教学平台(注释除外),判定以平台为准。

列表 10.4: 平台原始代码 (2)
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#任务二:创建股票涨跌幅DataFrame
import pandas as pd
data={  # 定义data字典,开始构建键值映射
    '中国卫星':[-0.0351,0.0172,-0.0035,-0.0246,0.0394],  # "中国卫星"的数据序列
    '中国软件':[-0.0139,0.0243,-0.0338,0.0146,0.0001],  # "中国软件"的数据序列
    '中国银行':[-0.0139,0.0243,-0.0338,0.0146,0.0001],  # "中国银行"的数据序列
    '上汽集团':[0.0212,0.0021,-0.0298,-0.0027,-0.0143]  # "上汽集团"的数据序列
}  # 数据结构定义结束
index=['20200525','20200526','20200527','20200528','20200529']  # 定义列表index
df=pd.DataFrame(data,index)  # 创建数据框df
print(df)  # 输出数据框数据

预期输出(本机 Python 实际运行结果,具体以平台运行结果为准):

            中国卫星    中国软件    中国银行    上汽集团
20200525 -0.0351 -0.0139 -0.0139  0.0212
20200526  0.0172  0.0243  0.0243  0.0021
20200527 -0.0035 -0.0338 -0.0338 -0.0298
20200528 -0.0246  0.0146  0.0146 -0.0027
20200529  0.0394  0.0001  0.0001 -0.0143

数据观察:两列完全相同? 仔细比对上表会发现,中国软件中国银行 两列的数值逐行完全一样。两只不同行业的股票连续五天涨跌幅恰好全部相同的概率几乎为零,这更像是原始数据录入时的复制粘贴痕迹(数据质量缺陷),而非真实行情。课堂不妨将其作为一个”找数据异常”的小练习:让学生用 df['中国软件'].equals(df['中国银行']) 自行验证——真实数据中这类”看起来太整齐”的列,往往是后续数据清洗环节(参见第16章缺失值与异常处理)最先要排查的对象。

代码深度解析:

  1. DataFrame的结构:

    DataFrame = {
        Index: 行标签(日期、ID等)
        Columns: 列名(变量名)
        Values: 二维NumPy数组
    }
  2. 从字典创建: 键自动成为列名

  3. 设置行索引:

    • 字符串: 日期、股票代码等
    • DatetimeIndex: 时间序列(推荐)
    • RangeIndex: 默认整数索引

10.5.2 其他创建DataFrame的方法

列表 10.5: 创建DataFrame的其他常用方法
# =============================================================================
# 题目:演示创建DataFrame的其他常用方法
# =============================================================================
# 本代码块演示从列表、元组、字典列表和NumPy数组创建DataFrame。
# 金融应用场景:根据数据来源不同,灵活选择创建方法。
# =============================================================================

# ==================== 方法1:从列表的列表创建 ====================
data_list = [  # 列表的列表,每个子列表代表一行数据
    ['中信证券', 24.78, 0.05],  # 第1行:名称、价格、涨跌幅
    ['国泰君安', 20.15, -0.02], # 第2行:名称、价格、涨跌幅
    ['海通证券', 14.03, 0.03]   # 第3行:名称、价格、涨跌幅
]
df1 = pd.DataFrame(data_list, columns=['名称', '价格', '涨跌幅'])  # 指定列名
print("方法1 - 从列表的列表:")
print(df1)
# 输出解释:每行代表一只证券,列分别为名称、价格、涨跌幅

# ==================== 方法2:从元组列表创建 ====================
data_tuples = [  # 元组列表,每个元组代表一行数据
    ('600519.SH', '贵州茅台', 1850.00),  # 第1行:代码、名称、价格
    ('000858.SZ', '五粮液', 220.50),     # 第2行:代码、名称、价格
    ('600036.SH', '招商银行', 45.20)     # 第3行:代码、名称、价格
]
df2 = pd.DataFrame(data_tuples, columns=['代码', '名称', '价格'])  # 指定列名
print("\n方法2 - 从元组列表:")
print(df2)
# 输出解释:每行代表一只股票,包含代码、名称和价格信息

# ==================== 方法3:从字典列表创建 ====================
data_dict_list = [  # 字典列表,每个字典代表一行数据
    {'code': '600519.SH', 'name': '贵州茅台', 'price': 1850.00},  # 第1行数据
    {'code': '000858.SZ', 'name': '五粮液', 'price': 220.50},      # 第2行数据
    {'code': '600036.SH', 'name': '招商银行', 'price': 45.20}      # 第3行数据
]
df3 = pd.DataFrame(data_dict_list)  # 字典的键自动成为列名
print("\n方法3 - 从字典列表:")
print(df3)
# 输出解释:字典的键(code, name, price)自动成为列名

# ==================== 方法4:从NumPy数组创建 ====================
np.random.seed(42)  # 设置随机种子,确保随机数表可重现
arr = np.random.randn(5, 3)  # 生成5行3列的标准正态分布随机数
df4 = pd.DataFrame(  # 从NumPy数组创建DataFrame
    arr,  # 数据源
    index=['row1', 'row2', 'row3', 'row4', 'row5'],  # 自定义行索引
    columns=['A', 'B', 'C']  # 自定义列名
)
print("\n方法4 - 从NumPy数组:")
print(df4)
# 输出解释:5行3列的随机数表,行为row1-row5,列为A-C

10.6 DataFrame的基本操作

10.6.1 访问数据

列表 10.6: 访问DataFrame中的数据
# =============================================================================
# 题目:演示访问DataFrame中数据的多种方法
# =============================================================================
# 本代码块演示如何访问DataFrame的列、行和单个值。
# 金融应用场景:提取特定股票数据、查询特定日期的数据、获取单个价格值。
# =============================================================================

# ==================== 重新创建示例DataFrame ====================
data = {  # 准备字典数据
    '中国卫星': [-0.0351, 0.0172, -0.0035, -0.0246, 0.0394],
    '中国软件': [-0.0139, 0.0243, -0.0338, 0.0146, 0.0001],
    '中国银行': [-0.0139, 0.0243, -0.0338, 0.0146, 0.0001],
    '上汽集团': [0.0212, 0.0021, -0.0298, -0.0027, -0.0143]
}
index = ['20200525', '20200526', '20200527', '20200528', '20200529']  # 日期索引
df = pd.DataFrame(data, index=index)  # 创建DataFrame

# ==================== 1. 访问列(推荐方式) ====================
print("方法1 - 使用点号(列名无空格):")
print(df.中国卫星.head(3))  # 使用点号访问列,head(3)显示前3行
# 输出解释:显示中国卫星列的前3个值(-0.0351, 0.0172, -0.0035)

print("\n方法2 - 使用方括号(通用):")
print(df['中国软件'].head(3))  # 使用方括号访问列(通用方法)
# 输出解释:显示中国软件列的前3个值

print("\n方法3 - 同时访问多列:")
print(df[['中国卫星', '中国软件']].head(3))  # 使用列表访问多列
# 输出解释:显示前3行,包含中国卫星和中国软件两列

# ==================== 2. 访问行 ====================
print("\n使用loc访问行(按标签):")
print(df.loc['20200526'])  # 使用loc按标签访问单行
# 输出解释:显示20200526这一行的所有列数据

print(df.loc[['20200526', '20200527']])  # 使用loc按标签访问多行
# 输出解释:显示20200526和20200527两行的所有列数据

print("\n使用iloc访问行(按位置):")
print(df.iloc[0])  # 使用iloc按位置访问第1行(位置0)
# 输出解释:显示第1行(20200525)的所有列数据

print(df.iloc[0:2])  # 使用iloc按位置访问前2行
# 输出解释:显示前2行(位置0和1)

# ==================== 3. 访问单个值 ====================
print("\n访问单个值:")
print(f"df.loc['20200526', '中国卫星'] = {df.loc['20200526', '中国卫星']:.4f}")  # 使用loc访问
# 输出解释:显示20200526这一天中国卫星的涨跌幅(0.0172)

print(f"df.iloc[0, 0] = {df.iloc[0, 0]:.4f}")  # 使用iloc访问
# 输出解释:显示第1行第1列的值(-0.0351)

10.6.2 DataFrame的基本信息

列表 10.7: 查看DataFrame的基本信息
# =============================================================================
# 题目:查看DataFrame的结构信息和统计摘要
# =============================================================================
# 本代码块演示如何快速了解DataFrame的基本情况。
# 金融应用场景:数据探索性分析(EDA),检查数据质量,了解数据分布。
# =============================================================================

print("DataFrame形状(行, 列):")
print(df.shape)  # 返回元组(行数, 列数)
# 输出解释:(5, 4)表示5行4列

print("\n前3行数据:")
print(df.head(3))  # 显示前3行数据
# 输出解释:显示20200525-20200527共3行的所有列数据

print("\n后3行数据:")
print(df.tail(3))  # 显示后3行数据
# 输出解释:显示20200527-20200529共3行的所有列数据

print("\n数据类型:")
print(df.dtypes)  # 显示每列的数据类型
# 输出解释:所有列都是float64类型(浮点数)

print("\n基本统计信息:")
print(df.describe())  # 显示描述性统计(均值、标准差、最小值、分位数、最大值)
# 输出解释:对每列计算count、mean、std、min、25%、50%、75%、max

print("\nDataFrame信息:")
print(df.info())  # 显示DataFrame的详细信息(包括内存使用)
# 输出解释:显示列名、非空值数量、数据类型、内存占用等

print("\n转置DataFrame:")
print(df.T)  # 转置DataFrame(行列互换)
# 输出解释:原来的行变成列,原来的列变成行

10.6.3 数据筛选与查询

列表 10.8: DataFrame的数据筛选与查询
# =============================================================================
# 题目:使用条件表达式筛选DataFrame数据
# =============================================================================
# 本代码块演示如何根据条件筛选DataFrame的行。
# 金融应用场景:找出涨幅为正的交易日,找出满足多个条件的记录。
# =============================================================================

# ==================== 1. 条件筛选 ====================
print("中国卫星涨幅超过0的交易日:")
positive = df[df.中国卫星 > 0]  # 使用布尔条件筛选行
print(positive)
# 输出解释:显示中国卫星涨跌幅大于0的两个交易日(20200526, 20200529)

# ==================== 2. 多条件筛选 ====================
print("\n中国卫星>0 且 中国软件>0的交易日:")
multi_condition = df[(df.中国卫星 > 0) & (df.中国软件 > 0)]  # 使用&连接条件(且)
print(multi_condition)
# 输出解释:显示两只股票同时上涨的交易日

# ==================== 3. 使用query方法(更直观) ====================
print("\n使用query筛选:")
query_result = df.query('中国卫星 > 0 and 中国软件 > 0')  # 使用query方法
print(query_result)
# 输出解释:结果与上一步相同,但语法更直观

# ==================== 4. 选择特定列 ====================
print("\n选择特定列:")
selected = df[['中国卫星', '上汽集团']]  # 选择两列
print(selected.head())  # 显示前几行
# 输出解释:只显示中国卫星和上汽集团两列的数据

10.7 Series与DataFrame的运算

10.7.1 算术运算

Pandas支持向量化的算术运算,并自动对齐索引。

列表 10.9: Pandas的算术运算与对齐机制
# =============================================================================
# 题目:演示Pandas的向量化算术运算和索引对齐机制
# =============================================================================
# 本代码块演示Series和DataFrame的算术运算,以及Pandas强大的索引对齐功能。
# 金融应用场景:计算股票收益率差,计算投资组合收益,计算价格变化。
# =============================================================================

# ==================== 创建两个Series ====================
s1 = pd.Series([10, 20, 30], index=['a', 'b', 'c'])  # 第1个Series
s2 = pd.Series([5, 15, 25], index=['a', 'b', 'd'])  # 第2个Series(注意索引不同)

print("Series 1:")
print(s1)
print("\nSeries 2:")
print(s2)
# 输出解释:s1索引为a,b,c;s2索引为a,b,d

# ==================== 基本运算(自动对齐索引) ====================
print("\n加法(自动对齐):")
print(s1 + s2)
# 输出解释:a:15(a对a), b:35(b对b), c:NaN(c无对应), d:NaN(d无对应)
#          索引对齐机制:只有相同索引的值才会相加,不同的产生NaN

print("\n减法:")
print(s1 - s2)
# 输出解释:a:5, b:5, c:NaN, d:NaN

print("\n乘法:")
print(s1 * s2)
# 输出解释:a:50, b:300, c:NaN, d:NaN

print("\n除法:")
print(s1 / s2)
# 输出解释:a:2.0, b:1.33, c:NaN, d:NaN

# ==================== DataFrame运算 ====================
df1 = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})  # 第1个DataFrame
df2 = pd.DataFrame({'A': [10, 20, 30], 'B': [40, 50, 60]})  # 第2个DataFrame

print("\nDataFrame加法:")
print(df1 + df2)
# 输出解释:A列:[11, 22, 33], B列:[44, 55, 66]

# ==================== 标量运算(广播) ====================
print("\nDataFrame乘以标量:")
print(df1 * 10)  # DataFrame乘以标量,所有元素都乘以10
# 输出解释:A列:[10, 20, 30], B列:[40, 50, 60]

10.7.2 统计函数

列表 10.10: Pandas统计函数在金融分析中的应用
# =============================================================================
# 题目:使用Pandas统计函数进行金融数据分析
# =============================================================================
# 本代码块演示常用的统计函数,以及按行或按列计算。
# 金融应用场景:计算平均收益率、波动率、累积收益率等关键指标。
# =============================================================================

print("每只股票的统计信息:")

# ==================== 按列计算(每只股票所有交易日) ====================
print("\n均值:")
print(df.mean())  # 计算每列的平均值(默认axis=0)
# 输出解释:显示每只股票5个交易日的平均涨跌幅

print("\n标准差:")
print(df.std())  # 计算每列的标准差(衡量波动性)
# 输出解释:标准差越大,股票波动越大

print("\n最大值:")
print(df.max())  # 计算每列的最大值
# 输出解释:显示每只股票最大的单日涨幅

print("\n最小值:")
print(df.min())  # 计算每列的最小值
# 输出解释:显示每只股票最大的单日跌幅

print("\n中位数:")
print(df.median())  # 计算每列的中位数
# 输出解释:排序后位于中间的值

# ==================== 按行计算(每交易日所有股票) ====================
print("\n按行计算(横向):")
print(df.mean(axis=1))  # axis=1表示沿行方向计算(每行的均值)
# 输出解释:每个交易日4只股票的平均涨跌幅

# ==================== 按列计算(每只股票所有交易日) ====================
print("\n按列计算(纵向):")
print(df.mean(axis=0))  # axis=0表示沿列方向计算(每列的均值,默认)
# 输出解释:与df.mean()相同,每只股票的平均涨跌幅

# ==================== 累积收益率计算 ====================
print("\n累积收益率:")
cum_returns = (1 + df).cumprod() - 1  # 计算累积收益率
print(cum_returns)
# 输出解释:
# (1+df)将涨跌幅转换为收益率因子(如-0.01变为0.99)
# cumprod()计算累积乘积
# -1将因子转换回收益率
# 最终结果是累积收益率(假设初始投资为1)

10.8 缺失值处理

列表 10.11: Pandas缺失值的识别与处理
# =============================================================================
# 题目:演示缺失值的检测、删除和填充方法
# =============================================================================
# 本代码块演示如何处理数据中的缺失值(NaN)。
# 金融应用场景:处理停牌日的缺失价格,填充股票未交易日的数据。
# =============================================================================

# ==================== 创建包含缺失值的DataFrame ====================
df_with_nan = pd.DataFrame({  # 创建包含NaN的DataFrame
    'A': [1, 2, np.nan, 4],      # 第3个值为NaN
    'B': [5, np.nan, np.nan, 8], # 第2、3个值为NaN
    'C': [9, 10, 11, 12]         # 无缺失值
})

print("包含缺失值的DataFrame:")
print(df_with_nan)
# 输出解释:NaN表示Not a Number,即缺失值

# ==================== 1. 检测缺失值 ====================
print("\n缺失值位置:")
print(df_with_nan.isnull())  # 检测每个值是否为缺失值
# 输出解释:True表示缺失值,False表示有效值

# ==================== 2. 删除缺失值 ====================
print("\n删除包含缺失值的行:")
print(df_with_nan.dropna())  # 删除包含任何缺失值的行
# 输出解释:只保留第1行和第4行(完整无缺失)

print("\n删除包含缺失值的列:")
print(df_with_nan.dropna(axis=1))  # 删除包含任何缺失值的列
# 输出解释:只保留C列(完全无缺失)

# ==================== 3. 填充缺失值 ====================
print("\n用0填充:")
print(df_with_nan.fillna(0))  # 用0填充所有缺失值
# 输出解释:所有NaN都被替换为0

print("\n用前向填充填充:")
print(df_with_nan.ffill())  # 前向填充(用前一个值填充)
# 输出解释:A列第3行用2填充,B列第2、3行用5填充(第4行本来就是8)

print("\n用均值填充:")
print(df_with_nan.fillna(df_with_nan.mean()))  # 用每列的均值填充
# 输出解释:A列用均值2.33填充,B列用均值6.5填充

10.9 本章小结

要点:

  • Series = 值 + 标签索引;DataFrame = 行列标签的二维表,从字典创建时键自动成为列名
  • [] 取列,.loc 按标签取数(切片含端点),.iloc 按位置取数(切片不含端点)
  • 向量化运算自动按索引对齐,索引对不上的位置产生 NaN
  • shape/dtypes/describe/info 是拿到新数据框后的第一批检查动作

易错点:

  • 标签切片 s['a':'c'] 含端点,位置切片 s[0:2] 不含端点,两套规则不要混
  • 两个索引不同的 Series 相加,缺失对齐处得 NaN 而不是 0
  • df.列名 在列名含空格或与 DataFrame 方法重名时失效,通用写法是 df['列名']

10.10 动手与思考

以下练习每题附参考答案(默认折叠)。请先独立完成并写下你的判断,再点开对照,最后上机验证。

  1. 输出预测:不运行代码,先写出下面代码的输出结果,再上机检验你的判断。

    import pandas as pd
    
    s = pd.Series([2, 4, 6], index=['a', 'b', 'c'])
    print(s['b'])
    print(s.iloc[1])
    t = pd.Series([10, 20, 30], index=['a', 'b', 'd'])
    print(s + t)

    参考答案(先写下你的预测再点开)

    解题思路:逐行推演。s['b'] 按标签索引取值,标签 b 对应 4;s.iloc[1] 按整数位置取值,位置 1(第 2 个元素)同样是 4——本例标签与位置恰好指向同一个元素,但两者依据完全不同。关键是 s + t:两个 Series 相加时 pandas 自动按索引对齐,a 是 2+10=12,b 是 4+20=24;c 只在 s 中、d 只在 t 中,对不上的位置无法相加,得 NaN。结果索引是两个索引的并集,且因为出现了 NaN,dtype 从 int 变为 float64。

    # 验证脚本:逐行输出四个表达式的求值结果
    import pandas as pd  # 导入Pandas库
    
    s = pd.Series([2, 4, 6], index=['a', 'b', 'c'])  # 创建带字母标签的Series
    print(s['b'])  # 按标签取值
    print(s.iloc[1])  # 按位置取值
    t = pd.Series([10, 20, 30], index=['a', 'b', 'd'])  # 第二个Series,索引部分不同
    print(s + t)  # 相加时按索引自动对齐,缺失对齐处为NaN

    预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):

    4
    4
    a    12.0
    b    24.0
    c     NaN
    d     NaN
    dtype: float64

    回扣本章:对应本章小结“要点”第 3 条与“易错点”第 2 条——向量化运算自动按索引对齐,索引对不上的位置产生 NaN 而不是 0。

  2. 概念辨析:s['b']s.iloc[1] 都能取到 4,两种访问方式的依据有何不同?df['中国卫星']df[['中国卫星']] 返回的对象类型分别是什么?

    参考答案(点开前请先独立完成)

    解题思路:逐点作答。第一,s['b'] 依据的是标签——到索引里查名字叫 b 的那一行;s.iloc[1] 依据的是位置——取排序后的第 2 个元素。本题中标签 b 恰好排在位置 1,所以两者都取到 4;但若把索引改成 ['c', 'b', 'a']s['b'] 仍是 4,s.iloc[1] 取到的却是 a 对应的 6,两种依据的差异立刻显现。第二,df['中国卫星'] 用单个字符串作键,取出的是一列,返回 Series(带 Name: 中国卫星);df[['中国卫星']] 用列表作键,语义是“取列表中的这些列”,返回只含一列的 DataFrame。后者是取多列写法 df[['列1', '列2']] 在单列上的特例,因此保留了二维表结构。

    # 验证脚本:对比两种取列方式的返回类型
    import pandas as pd  # 导入Pandas库
    
    df_demo = pd.DataFrame({'中国卫星': [-0.0035, 0.0172], '上汽集团': [0.0212, 0.0021]}, index=['第1日', '第2日'])  # 构造两列演示数据框
    print(df_demo['中国卫星'])  # 单个字符串作键:取出的是一列,返回Series
    print(df_demo[['中国卫星']])  # 列表作键:取出的是只含一列的DataFrame
    print(type(df_demo['中国卫星']))  # 验证返回类型
    print(type(df_demo[['中国卫星']]))  # 验证返回类型

    预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):

    第1日   -0.0035
    第2日    0.0172
    Name: 中国卫星, dtype: float64
           中国卫星
    第1日 -0.0035
    第2日  0.0172
    <class 'pandas.core.series.Series'>
    <class 'pandas.core.frame.DataFrame'>

    回扣本章:对应本章小结“要点”第 1、2 条与“易错点”第 3 条——[] 取列时单层括号得到 Series、双层括号得到单列 DataFrame,通用写法是 df['列名']

  3. 变式任务(平台任务一同型改造):仿照平台任务一,为你自选的 4 只股票构造某日涨跌幅 Series(索引用股票简称);再仿照任务二把它扩展为 5 个交易日 × 4 只股票的 DataFrame,并用 describe() 查看每列的统计摘要。

    参考答案(点开前请先独立完成)

    解题思路:分两步仿写。第一步仿照 列表 10.2:把 4 个涨跌幅数值放进列表、4 个股票简称放进名称列表,pd.Series(数据列表, index=名称列表) 即得带标签索引的 Series。第二步仿照 列表 10.4:为每只股票准备 5 个交易日的涨跌幅列表,组成“列名→列表”的字典,再 pd.DataFrame(字典, 交易日列表) 得到 5 行 × 4 列的数据框;describe() 随后给出每列的 count、mean、std、min 与四分位数。下方代码中的涨跌幅为自选的演示数值,重点是流程与结构;你应换成自己选定股票的真实涨跌幅。

    # 变式程序:自选4只股票的涨跌幅Series与5日DataFrame
    import pandas as pd  # 导入Pandas库
    
    list_change = [0.012, -0.008, 0.015, -0.021]  # 自选4只股票某日涨跌幅(演示数值)
    name_stock = ['招商银行', '宁波银行', '三一重工', '中兴通讯']  # 股票简称作为标签索引
    s_change = pd.Series(list_change, index=name_stock)  # 创建带标签索引的Series
    print(s_change)  # 输出某日涨跌幅Series
    data_change = {  # 每只股票5个交易日的涨跌幅(演示数值)
        '招商银行': [0.012, -0.006, 0.003, 0.015, -0.002],
        '宁波银行': [-0.008, 0.011, -0.004, 0.009, 0.006],
        '三一重工': [0.015, 0.021, -0.012, 0.008, -0.005],
        '中兴通讯': [-0.021, 0.017, -0.009, 0.013, 0.020]
    }
    index_days = ['20240506', '20240507', '20240508', '20240509', '20240510']  # 5个交易日标签
    df_change = pd.DataFrame(data_change, index_days)  # 创建5行×4列的DataFrame
    print(df_change)  # 输出数据框
    print(df_change.describe())  # 每列的统计摘要

    预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):

    招商银行    0.012
    宁波银行   -0.008
    三一重工    0.015
    中兴通讯   -0.021
    dtype: float64
               招商银行   宁波银行   三一重工   中兴通讯
    20240506  0.012 -0.008  0.015 -0.021
    20240507 -0.006  0.011  0.021  0.017
    20240508  0.003 -0.004 -0.012 -0.009
    20240509  0.015  0.009  0.008  0.013
    20240510 -0.002  0.006 -0.005  0.020
               招商银行      宁波银行      三一重工      中兴通讯
    count  5.000000  5.000000  5.000000  5.000000
    mean   0.004400  0.002800  0.005400  0.004000
    std    0.008961  0.008349  0.013722  0.018028
    min   -0.006000 -0.008000 -0.012000 -0.021000
    25%   -0.002000 -0.004000 -0.005000 -0.009000
    50%    0.003000  0.006000  0.008000  0.013000
    75%    0.012000  0.009000  0.015000  0.017000
    max    0.015000  0.011000  0.021000  0.020000

    注意:以上为本题变式的独立代码;列表 10.2列表 10.4 对应的平台原始代码块仍须原样输入教学平台,不要用本变式替换。

    回扣本章:对应本章小结“要点”第 1 条——列表加索引创建 Series、字典加行索引创建 DataFrame,describe() 是拿到新数据框后的第一批检查动作之一。

  4. 动手验证:对平台任务二得到的数据框 df,运行 df['中国软件'].equals(df['中国银行']) 确认两列是否完全相同;再分别运行 df.mean(axis=0)df.mean(axis=1),说明 axis 取值对应的计算方向。

    参考答案(点开前请先独立完成)

    解题思路:先运行 列表 10.4 得到 df(或按该代码块的数据在本地重建同一数据框),再执行三行验证。equals 逐元素比较两列,返回 True 即印证正文“数据观察”一段的判断:中国软件中国银行 两列逐行完全相同,是数据录入缺陷而非真实行情。df.mean(axis=0) 中的 axis=0 指沿行方向压缩(行被消掉),对每求均值,即每只股票 5 个交易日的平均涨跌幅;df.mean(axis=1) 沿列方向压缩(列被消掉),对每求均值,即每个交易日 4 只股票的平均涨跌幅。记忆口径:axis 是“被消掉的轴”,不是“保留的方向”。

    # 验证脚本:先按 @lst-ch10-platform-task-2 的数据在本地重建同一 df,再执行三项检查
    import pandas as pd  # 导入Pandas库
    data = {'中国卫星': [-0.0351, 0.0172, -0.0035, -0.0246, 0.0394],  # 四只股票的5日涨跌幅
            '中国软件': [-0.0139, 0.0243, -0.0338, 0.0146, 0.0001],
            '中国银行': [-0.0139, 0.0243, -0.0338, 0.0146, 0.0001],  # 与中国软件逐行相同
            '上汽集团': [0.0212, 0.0021, -0.0298, -0.0027, -0.0143]}
    df = pd.DataFrame(data, index=['20200525', '20200526', '20200527', '20200528', '20200529'])  # 重建平台任务二的df
    print(df['中国软件'].equals(df['中国银行']))  # 逐元素比较两列是否完全相同
    print(df.mean(axis=0))  # axis=0:沿行方向压缩,得到每列(每只股票)的均值
    print(df.mean(axis=1))  # axis=1:沿列方向压缩,得到每行(每个交易日)的均值

    预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):

    True
    中国卫星   -0.00132
    中国软件   -0.00174
    中国银行   -0.00174
    上汽集团   -0.00470
    dtype: float64
    20200525   -0.010425
    20200526    0.016975
    20200527   -0.025225
    20200528    0.000475
    20200529    0.006325
    dtype: float64

    回扣本章:对应本章学习目标第 4 条与正文“数据观察”段——按行或按列(axis)计算指标时先想清楚“要消掉哪个轴”;两列均值完全相同(-0.00174)正是它们逐行相等的直接后果。